package ro.sergiu.finder;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.table.AbstractTableModel;
import ro.sergiu.finder.models.columns.ColumnModel;
import ro.sergiu.finder.models.finder.db.DbFinderModel;
import ro.sergiu.finder.ui.FindDialog;

/**
 *
 * @author Toporjinschi
 * @param <T>
 */
public class MetaDataTableModel<T> extends AbstractTableModel {

    private class TableRow {

        private final T bean;
        private final List<Object> row;

        public TableRow(T bean, List<Object> row) {
            this.bean = bean;
            this.row = row;
        }

        public T getBean() {
            return bean;
        }

        public List<Object> getRow() {
            return row;
        }
    }
    private final ResultSet rs;
    private final List<ColumnModel> defFields;
    private final List<Class> modelTypes = new ArrayList<Class>();
    private final List<TableRow> rows = new ArrayList<TableRow>();
    private final DbFinderModel<T> finderModel;
    private final ResultSetMetaData metaData;
    private List<ColumnModel> visibleColumns = new ArrayList<ColumnModel>();

    public MetaDataTableModel(ResultSet rs, DbFinderModel finderModel) throws SQLException {
        this.finderModel = finderModel;
        this.rs = rs;
        this.defFields = this.finderModel.getColumns();
        this.visibleColumns = this.finderModel.getVisibleColumns();
        this.metaData = rs.getMetaData();
        getValues();
    }

    @Override
    public String getColumnName(int column) {
        return visibleColumns.get(column).getColumnName();
    }

    @Override
    public int getColumnCount() {
        return visibleColumns.size();
    }

    @Override
    public Class getColumnClass(int columnIndex) {
        if (modelTypes.isEmpty()) {
            return Object.class;
        }
        return modelTypes.get(columnIndex);
    }

    @Override
    public int getRowCount() {
        return rows == null ? 0 : rows.size();
    }

    @Override
    public Object getValueAt(int row, int column) {
        return rows.get(row).getRow().get(column);
    }

    public T getBean(int row) {
        return rows.get(row).getBean();
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    private void getValues() throws SQLException {
        while (rs.next()) {
            List<Object> rowForTable = new ArrayList<Object>();
            for (ColumnModel resultColumn : defFields) {
                Object dbValue = getValue(rs, (String) resultColumn.getMapItem());
                if (!resultColumn.isVisible()) {
                    continue;
                }
                try {
                    dbValue = resultColumn.getFormatter().format(dbValue, rs);
                } catch (SQLException ex) {
                    Logger.getLogger(MetaDataTableModel.class.getName()).log(Level.SEVERE, null, ex);
                }
                if (rs.isFirst()) {
                    modelTypes.add(dbValue.getClass());
                }
                rowForTable.add(dbValue);
            }
            T bean = finderModel.populateBean(rs);
            rows.add(new TableRow(bean, rowForTable));
        }
    }

    private Object getValue(ResultSet rs, String colName) throws SQLException {
        int colIdx = rs.findColumn(colName);
        int sqlType = metaData.getColumnType(colIdx);
        switch (sqlType) {
            case Types.NUMERIC:
            case Types.INTEGER:
                return rs.getInt(colName);
            case Types.LONGVARBINARY:
            case Types.LONGNVARCHAR:
            case Types.LONGVARCHAR:
            case Types.VARBINARY:
            case Types.VARCHAR:
            case Types.NVARCHAR:
            case Types.NCHAR:
            case Types.CHAR:
                return rs.getString(colName);
            case Types.BIGINT:
                return rs.getLong(colName);
            case Types.BOOLEAN:
                return rs.getBoolean(colName);
            case Types.DATE:
                return new Date(rs.getDate(colName).getTime());
            case Types.TIMESTAMP:
                return new Date(rs.getTimestamp(colName).getTime());
            case Types.FLOAT:
                return rs.getFloat(colName);
            case Types.DOUBLE:
                return rs.getDouble(colName);
            case Types.SMALLINT:
                return rs.getShort(colName);
            default: {
                Logger.getLogger(FindDialog.class.getName()).log(Level.WARNING, "Invalid db type " + sqlType);
                return rs.getString(colName);
            }
        }
    }
}
